#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "rs274ngc.hh"
#include "rs274ngc_return.hh"
#include "rs274ngc_interp.hh"
#include "interp_internal.hh"

#include <algorithm>

bool Interp::has_user_mcode(setup_pointer settings, block_pointer block)
{
    unsigned i;
    for (i = 0; i < sizeof(block->m_modes) / sizeof(int); i++)
    {
        if (block->m_modes[i] == -1)
            continue;
        if (M_REMAPPABLE(block->m_modes[i]) && settings->m_remapped[block->m_modes[i]])
            return true;
    }
    return false;
}

bool Interp::remap_in_progress(const char* code)
{
    remap_pointer rp = remapping(code);
    if (rp == NULL)
        return false;
    for (int i = _setup.remap_level; i > 0; i--)
    {
        if (_setup.blocks[i].executing_remap == rp)
        {
            // printf("---------------- remap_in_progress(%s): TRUE level=%d\n",code,i);
            return true;
        }
    }
    // printf("---------------- remap_in_progress(%s): FALSE\n",code);
    return false;
}

int Interp::convert_remapped_code(block_pointer block, setup_pointer settings, int phase, char letter, int number)
{
    return -1;
#if 0
    remap_pointer remap;
    char key[2];
    int status;
    block_pointer cblock;
    bp::list plist;
    char cmd[LINELEN];

    if (number == -1)
        logRemap("convert_remapped_code '%c'", letter);
    else
        logRemap("convert_remapped_code '%c%d'", letter, number);

    switch (toupper(letter))
    {
    case 'M':
        remap = settings->m_remapped[number];
        break;
    case 'G':
        remap = settings->g_remapped[number];
        break;
    default:
        key[0] = letter;
        key[1] = '\0';
        remap = remapping((const char*)key);
    }
    CHKS((remap == NULL), INTERP_ERR_SYNTAX_FAILED, "BUG: convert_remapped_code: no remapping");

    // remapped handlers may use Python code to
    // setup environment before, and finish work after doing theirs.
    // That's what prolog and epilog functions are for.
    // These are described in the remap descriptor as read from ini.

    // Since a remap is always executed in the context of a controlling block,
    // this block now contains fields which hold dynamic remap information, like
    // the breadcrumbs execution trail.
    // Some of these fields are initialized here -
    // conceptually the block stack is also a 'remap frame stack'.

    // the O_call code will pick up the static descriptor and
    // dynamic information through the block and call any prolog
    // function before passing control to the actual handler procedure.

    // On the corresponding O_endsub/O_return, any epilog function
    // will be executed, doing any work not doable in an NGC file.

    // Note that even Python-remapped execution is pulled through the
    // oword mechanism - so no duplication of handler calling code
    // is needed.

    snprintf(cmd, sizeof(cmd), "O <%s> call ", REMAP_FUNC(remap));

    // the controlling block holds all dynamic remap information.
    cblock = &CONTROLLING_BLOCK(*settings);
    cblock->executing_remap = remap;  // the current descriptor
    cblock->param_cnt = 0;

    if (remap->argspec && (strchr(remap->argspec, '@') != NULL))
    {
        // append a positional argument list instead of local variables
        // if user specified '@'
        // named local params are dealt with in execute_call() when
        // the new call frame is fully established
        CHP(add_parameters(settings, cblock, &cmd[strlen(cmd)]));
    }

    if ((_setup.debugmask & ZUC_DEBUG_REMAP) && (_setup.loggingLevel > 2))
    {
        logRemap("convert_remapped_code(%s)", cmd);
    }

    // good to go, pass to o-word call handling mechanism
    status = read(cmd);
    block_pointer eblock = &EXECUTING_BLOCK(*settings);
    eblock->call_type = CT_REMAP;
    CHKS(status != INTERP_OK, INTERP_ERR_SYNTAX_FAILED, "convert_remapped_code: inital read returned %s", interp_status(status));
    return (-phase);
#endif
}

// add_parameters - a built-in prolog function
//
// handles argspec and extracts required and optional items from the
// controlling block.
//
// if preparing for an NGC file, add local variables to
// the current oword subroutine call frame
//
// if posargs == NULL:
//      add the named parameters as local variables to  the current call frame
// if posargs != NULL:
//      create a positional argument list as per argspec order
//      instead of adding local variables
//
// also, generate a kwargs style dictionary of required and optional items
// in case a Python prolog is called
//
// 1. add all requried and  present optional words.
// 2. error on missing but required words.
// 4. handle '>' as to require a positive feed.
// 5. handle '^' as to require a positive speed.
// 6. handle 'N' as to add the line number.
//
// return INTERP_ERROR and propagate appropriate message if any errors so far
// else return INTERP_OK
//
// handling '@' (positional params) is dealt with in the calling procedure

int Interp::add_parameters(setup_pointer settings, block_pointer cblock, char* posarglist)
{
    const char *s, *argspec, *code;
    block_pointer block;
    char missing[30], optional[30], required[30];
    char* m = missing;
    char* o = optional;
    char* r = required;
    char msg[LINELEN], tail[LINELEN];
    bool errored = false;
    remap_pointer rptr = cblock->executing_remap;
    context_pointer active_frame = &settings->sub_context[settings->call_level];

    if (!rptr)
    {
        ERS(INTERP_ERR_INTERNAL, "BUG: add_parameters: remap_frame: executing_remap == NULL ");
    }
    code = rptr->name;

    // if any Python handlers are present, create a kwargs dict

    std::fill(missing, std::end(missing), 0);
    std::fill(optional, std::end(optional), 0);
    std::fill(required, std::end(required), 0);
    std::fill(msg, std::end(msg), 0);
    std::fill(tail, std::end(tail), 0);

    s = argspec = rptr->argspec;
    CHKS((argspec == NULL), INTERP_ERR_SYNTAX_FAILED, "BUG: add_parameters: argspec = NULL");

    while (*s)
    {
        if (isupper(*s) && !strchr(required, *s))
            *r++ = tolower(*s);
        if (islower(*s) && !strchr(optional, *s))
            *o++ = *s;
        if (strchr(">^Nn", *s) && !strchr(required, *s))
            *r++ = *s;
        s++;
    }
    block = &CONTROLLING_BLOCK((*settings));

    logNP("add_parameters code=%s argspec=%s call_level=%d r=%s o=%s \n", code, argspec, settings->call_level, required, optional);

    /*
    // #define STORE(name,value)						\
//     if (pydict) {							\
// 		try {								\
// 			active_frame->pystuff.impl->kwargs[name] = ((jaka_value*)(&value))-> ## scalarValue;		\
// 		}								\
//         catch (bp::error_already_set) {					\
// 			PyErr_Print();						\
// 			PyErr_Clear();						\
// 			ERS(INTERP_ERR_CANNOT_ADD_PARAMETER,"add_parameters: cant add '%s' to args",name);		\
// 		}								\
//     }									\
//     if (posarglist) {							\
// 		char actual[LINELEN];						\
// 		snprintf(actual, sizeof(actual),"[%.4lf]", ((jaka_value*)(&value))-> ## scalarValue);		\
// 		strcat(posarglist, actual);					\
// 		cblock->param_cnt++;						\
//     } else {								\
// 		add_named_param(name,0);					\
// 		store_named_param(settings,name,value,0);			\
//     }
*/
    auto STORE = [&](const char* name, jaka_value value) -> int {
        if (posarglist)
        {
            char actual[LINELEN];
            snprintf(actual, sizeof(actual), "[%.4lf]", value.scalarValue);
            strcat(posarglist, actual);
            cblock->param_cnt++;
        }
        else
        {
            add_named_param(name, 0);
            store_named_param(settings, name, value, 0);
        }
        return INTERP_OK;
    };

    // #define PARAM(spec,name,flag,value)
    //     if ((flag)) { /* present */
    // 	/* required or optional */
    // 		if (strchr(required,spec) || strchr(optional,spec)) {
    // 			STORE(name,value);
    // 		}
    //     } else {
    // 		if (strchr(required,spec)) { /* missing */
    // 			*m++ = spec;
    // 			errored = true;
    // 		}
    //     }

    auto PARAM = [&](char spec, const char* name, bool flag, jaka_value value) {
        if ((flag))
        { /* present */
            /* required or optional */
            if (strchr(required, spec) || strchr(optional, spec))
            {
                STORE(name, value);
            }
        }
        else
        {
            if (strchr(required, spec))
            { /* missing */
                *m++ = spec;
                errored = true;
            }
        }
    };

    s = rptr->argspec;
    // step through argspec in order so positional args are built
    // in the correct order
    jaka_value tempVal;
    tempVal.valueType = SCALAR_VALUE;
    while (*s)
    {
        switch (tolower(*s))
        {
        case 'a':
            tempVal.scalarValue = block->a_number;
            PARAM('a', "a", block->a_flag, tempVal);
            break;
        case 'b':
            tempVal.scalarValue = block->b_number;
            PARAM('b', "b", block->b_flag, tempVal);
            break;
        case 'c':
            tempVal.scalarValue = block->c_number;
            PARAM('c', "c", block->c_flag, tempVal);
            break;
        case 'd':
            tempVal.scalarValue = block->d_number_float;
            PARAM('d', "d", block->d_flag, tempVal);
            break;
        case 'e':
            tempVal.scalarValue = block->e_number;
            PARAM('e', "e", block->e_flag, tempVal);
            break;
        case 'f':
            tempVal.scalarValue = block->f_number;
            PARAM('f', "f", block->f_flag, tempVal);
            break;
        case 'h':
            tempVal.scalarValue = (double)block->h_number;
            PARAM('h', "h", block->h_flag, tempVal);
            break;
        case 'i':
            tempVal.scalarValue = block->i_number;
            PARAM('i', "i", block->i_flag, tempVal);
            break;
        case 'j':
            tempVal.scalarValue = block->j_number;
            PARAM('j', "j", block->j_flag, tempVal);
            break;
        case 'k':
            tempVal.scalarValue = block->k_number;
            PARAM('k', "k", block->k_flag, tempVal);
            break;
        case 'l':
            tempVal.scalarValue = (double)block->l_number;
            PARAM('l', "l", block->l_flag, tempVal);
            break;
        case 'p':
            tempVal.scalarValue = block->p_number;
            PARAM('p', "p", block->p_flag, tempVal);
            break;
        case 'q':
            tempVal.scalarValue = block->q_number;
            PARAM('q', "q", block->q_flag, tempVal);
            break;
        case 'r':
            tempVal.scalarValue = block->r_number;
            PARAM('r', "r", block->r_flag, tempVal);
            break;
        case 's':
            tempVal.scalarValue = block->s_number;
            PARAM('s', "s", block->s_flag, tempVal);
            break;
        case 't':
            tempVal.scalarValue = (double)block->t_number;
            PARAM('t', "t", block->t_flag, tempVal);
            break;
        case 'u':
            tempVal.scalarValue = block->u_number;
            PARAM('u', "u", block->u_flag, tempVal);
            break;
        case 'v':
            tempVal.scalarValue = block->v_number;
            PARAM('v', "v", block->v_flag, tempVal);
            break;
        case 'w':
            tempVal.scalarValue = block->w_number;
            PARAM('w', "w", block->w_flag, tempVal);
            break;
        case 'x':
            tempVal.scalarValue = block->x_number;
            PARAM('x', "x", block->x_flag, tempVal);
            break;
        case 'y':
            tempVal.scalarValue = block->y_number;
            PARAM('y', "y", block->y_flag, tempVal);
            break;
        case 'z':
            tempVal.scalarValue = block->z_number;
            PARAM('z', "z", block->z_flag, tempVal);
            break;
        case '-':
            break;  // ignore - backwards compatibility
        default:;
        }
        s++;
    }

    s = missing;
    if (*s)
    {
        strcat(tail, " missing: ");
    }
    while (*s)
    {
        errored = true;
        char c = toupper(*s);
        strncat(tail, &c, 1);
        if (*(s + 1))
            strcat(tail, ",");
        s++;
    }
    // special cases:
    // N...add line number
    if (strchr(required, 'n') || strchr(required, 'N'))
    {
        jaka_value value;
        value.valueType = SCALAR_VALUE;
        value.scalarValue = (double)cblock->saved_line_number;
        STORE("n", value);
    }

    // >...require positive feed
    if (strchr(required, '>'))
    {
        if (settings->feed_rate > 0.0)
        {
            jaka_value value;
            value.valueType = SCALAR_VALUE;
            value.scalarValue = settings->feed_rate;
            STORE("f", value);
        }
        else
        {
            strcat(tail, "F>0,");
            errored = true;
        }
    }
    // ^...require positive speed
    if (strchr(required, '^'))
    {
        if (settings->speed > 0.0)
        {
            jaka_value value;
            value.valueType = SCALAR_VALUE;
            value.scalarValue = settings->speed;
            STORE("s", value);
        }
        else
        {
            strcat(tail, "S>0,");
            errored = true;
        }
    }

    if (errored)
    {
        ERS(INTERP_ERR_USER_DEFINED, "user-defined %s:%s", code, tail);
    }
    return INTERP_OK;
}

// this looks up a remapping by unnormalized code (like G88.1)
remap_pointer Interp::remapping(const char* code)
{
    remap_iterator n = _setup.remaps.find(code);
    if (n != _setup.remaps.end())
        return &n->second;
    else
        return NULL;
}

// parse options of the form:
// REMAP= M420 modalgroup=6 argspec=pq prolog=setnamedvars ngc=m43.ngc epilog=ignore_retvalue
// REMAP= M421 modalgroup=6 argspec=- prolog=setnamedvars python=m43func epilog=ignore_retvalue

int Interp::parse_remap(const char* inistring, int lineno)
{
    char iniline[LINELEN];
    char* argv[MAX_REMAPOPTS];
    int argc = 0;
    const char* code;
    remap r;
    bool errored = false;
    int g1 = 0, g2 = 0;
    int mcode = -1;
    int gcode = -1;
    char* s;

    memset((void*)&r, 0, sizeof(remap));
    r.modal_group = -1;  // mark as unset, required param for m/g
    r.motion_code = INT_MIN;
    strcpy(iniline, inistring);
    // strip trailing comments
    if ((s = strchr(iniline, '#')) != NULL)
    {
        *s = '\0';
    }
    s = strtok((char*)iniline, " \t");

    while (s != NULL && argc < MAX_REMAPOPTS - 1)
    {
        argv[argc++] = s;
        s = strtok(NULL, " \t");
    }
    if (argc == MAX_REMAPOPTS)
    {
        Error("parse_remap: too many arguments (max %d)", MAX_REMAPOPTS);
        goto fail;
    }
    argv[argc] = NULL;
    code = strstore(argv[0]);
    r.name = code;

    for (int i = 1; i < argc; i++)
    {
        int kwlen = 0;
        char* kw = argv[i];
        char* arg = strchr(argv[i], '=');
        if (arg != NULL)
        {
            kwlen = arg - argv[i];
            arg++;
            if (!strlen(arg))
            {  // 'kw='
                Error("option '%s' - zero length value: %d:REMAP = %s", kw, lineno, inistring);
                errored = true;
                continue;
            }
        }
        else
        {  // 'kw'
            Error("option '%s' - missing '=<value>: %d:REMAP = %s", kw, lineno, inistring);
            errored = true;
            continue;
            ;
        }
        if (!strncasecmp(kw, "modalgroup", kwlen))
        {
            r.modal_group = atoi(arg);
            continue;
        }
        if (!strncasecmp(kw, "argspec", kwlen))
        {
            size_t pos = strspn(arg, "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz>^@");
            if (pos != strlen(arg))
            {
                Error("argspec: illegal word '%c' - %d:REMAP = %s", arg[pos], lineno, inistring);
                errored = true;
                continue;
            }
            r.argspec = strstore(arg);
            continue;
        }
        if (!strncasecmp(kw, "ngc", kwlen))
        {
            if (r.remap_py)
            {
                Error("cant remap to an ngc file and a Python function: -  %d:REMAP = %s", lineno, inistring);
                errored = true;
                continue;
            }
            FILE* fp = find_ngc_file(&_setup, arg);
            if (fp)
            {
                r.remap_ngc = strstore(arg);
                fclose(fp);
            }
            else
            {
                Error("NGC file not found: ngc=%s - %d:REMAP = %s", arg, lineno, inistring);
                errored = true;
            }
            continue;
        }
        Error("unrecognized option '%*s' in  %d:REMAP = %s", kwlen, kw, lineno, inistring);
    }
    if (errored)
    {
        goto fail;
    }

    if (remapping(code))
    {
        Error("code '%s' already remapped : %d:REMAP = %s", code, lineno, inistring);
        goto fail;
    }

    // it is an error not to define a remap function to call.
    if ((r.remap_ngc == NULL) && (r.remap_py == NULL))
    {
        Error("code '%s' - no remap function given, use either 'python=<function>' or 'ngc=<basename>' : %d:REMAP = %s", code, lineno, inistring);
        goto fail;
    }

#define CHECK(bad, fmt, ...)         \
    do                               \
    {                                \
        if (bad)                     \
        {                            \
            Log(fmt, ##__VA_ARGS__); \
            goto fail;               \
        }                            \
    } while (0)

    switch (towlower(*code))
    {
    case 't':
    case 's':
    case 'f':
        CHECK((strlen(code) > 1), "%d: %c remap - only single letter code allowed", lineno, *code);
        CHECK((r.modal_group != -1), "%d: %c remap - modal group setting ignored - fixed sequencing", lineno, *code);
        _setup.remaps[code] = r;
        break;

    case 'm':
        if (sscanf(code + 1, "%d", &mcode) != 1)
        {
            Error("parsing M-code: expecting integer like 'M420', got '%s' : %d:REMAP = %s", code, lineno, inistring);
            goto fail;
        }
        if (r.modal_group == -1)
        {
            Error("warning: code '%s' : no modalgroup=<int> given, using default group %d : %d:REMAP = %s", code, MCODE_DEFAULT_MODAL_GROUP, lineno, inistring);
            r.modal_group = MCODE_DEFAULT_MODAL_GROUP;
        }
        if (!M_MODE_OK(r.modal_group))
        {
            Error("error: code '%s' : invalid modalgroup=<int> given (currently valid: 4..10) : %d:REMAP = %s", code, lineno, inistring);
            goto fail;
        }
        _setup.remaps[code] = r;
        _setup.m_remapped[mcode] = &_setup.remaps[code];
        break;
    case 'g':

        // code may be G88.1 or so - normalize to use 'G881' instead
        // (multiply by 10, no dots)
        if (sscanf(code + 1, "%d.%d", &g1, &g2) == 2)
        {
            gcode = g1 * 10 + g2;
        }
        if (gcode == -1)
        {
            if (sscanf(code + 1, "%d", &gcode) != 1)
            {
                Error("code '%s' : cant parse G-code : %d:REMAP = %s", code, lineno, inistring);
                goto fail;
            }
            gcode *= 10;
        }
        r.motion_code = gcode;
        if (r.modal_group == -1)
        {
            Error("warning: code '%s' : no modalgroup=<int> given, using default group %d : %d:REMAP = %s", code, GCODE_DEFAULT_MODAL_GROUP, lineno, inistring);
            r.modal_group = GCODE_DEFAULT_MODAL_GROUP;
        }
        if (!G_MODE_OK(r.modal_group))
        {
            Error("error: code '%s' : %s modalgroup=<int> given  : %d:REMAP = %s", argv[0], r.modal_group == -1 ? "no" : "invalid", lineno, inistring);
            goto fail;
        }
        _setup.remaps[code] = r;
        _setup.g_remapped[gcode] = &_setup.remaps[code];
        break;

    default:
        // make sure the python plugin is in a usable state if needed
        Log("REMAP BUG=%s %d:REMAP = %s", code, lineno, inistring);
    }
    return INTERP_OK;

fail:
    return INTERP_ERROR;
}
